#ifndef __ALIGN_RESULT_H__
#define __ALIGN_RESULT_H__


#include "container_common.h"
#include "container_util.h"

#define SUFFIX_META	".meta"


struct Item {
	string _qscore;
	unsigned int _pos;
	int _hit;
	char _strand;
	uchar2 _offset;
	char2 _sub;

	Item() {};
	Item(int readLength, const char* qscore, unsigned int pos, 
		int hit, char strand, uchar2 offset, char2 sub) : 
		_qscore(string(qscore, readLength)), _pos(pos), _hit(hit), 
		_strand(strand), _offset(offset), _sub(sub){};
};

inline bool ItemCompare(Item item1, Item item2) {
	return (item1._pos < item2._pos);
}


/**
 * this structure contains alignment result after SA conversion
 * for multiple alignments
 * !!!NOTE: this structure is for alignments for the same chromosome
 * */
struct AlignResult {
	unsigned long _numAlign;
	unsigned int _readLength;
	string _chrName;

	char* _qscore;		//the length is _readLength*_numAlign, without compression
	unsigned int* _pos;	//the length is _numAlign
	int* _hit;		//the length is _numAlign
	char* _strand;		//the length is _numAlign
	uchar2* _offset;	//the length is _numAlign
	char2* _sub;		//the length is _numAlign, -1 indicates no mismatch
	
	char* _ref;		//the reference

	AlignResult() {
		_numAlign = 0;
		_readLength = 0;
		_qscore = NULL;
		_pos = NULL;
		_hit = NULL;
		_strand = NULL;
		_offset = NULL;
		_sub = NULL;
		_ref = NULL;
	};

	AlignResult(const unsigned long numAlign, const int readLength) {
		alloc(numAlign, readLength);
	}

	AlignResult(string chrName, const unsigned long numAlign, const int readLength) {
		_chrName = chrName;
		alloc(numAlign, readLength);
	}

	~AlignResult() {
		close();
	}

	void setRef(char* ref) {
		_ref = ref;
	}

	void alloc(const unsigned long numAlign, const int readLength) {
		_numAlign = numAlign;
		_readLength = readLength;
		_qscore = new char[_readLength*_numAlign];
		_pos = new unsigned int[_numAlign];
		_hit = new int[_numAlign];
		_strand = new char[_numAlign];
		_offset = new uchar2[_numAlign];
		_sub = new char2[_numAlign];
	}

	void clear() {
		close();
	}


	//sort by _pos
	//using cpu based counting sort
	void cpu_sort() {
		INIT_TIMER;
		START_TIMER;

		//counting sort for the position
		unsigned int* id = new unsigned int[_numAlign];
		fillSequence(id, _numAlign, 0u);
		counting_sort2(_pos, id, _numAlign);

		//gather attributes hit, strand, offset, sub
		unsigned char* buf = new unsigned char[sizeof(int)*_numAlign];
		gather((int*)buf, _hit, id, _numAlign);
		memcpy(_hit, buf, sizeof(int)*_numAlign);
		gather((char*)buf, _strand, id, _numAlign);
		memcpy(_strand, buf, sizeof(char)*_numAlign);
		gather((uchar2*)buf, _offset, id, _numAlign);
		memcpy(_offset, buf, sizeof(uchar2)*_numAlign);
		gather((char2*)buf, _sub, id, _numAlign);
		memcpy(_sub, buf, sizeof(char2)*_numAlign);
		delete[] buf;

		//gather the attribute qscore
		char* qscoreBuf = new char[_readLength*_numAlign];
		for(unsigned long i = 0; i < _numAlign; i++) {
			memcpy(qscoreBuf + i*_readLength, _qscore + id[i]*_readLength, 
					sizeof(char)*_readLength);
		}
		memcpy(_qscore, qscoreBuf, sizeof(char)*_readLength*_numAlign);
		delete[] qscoreBuf;
		
		delete[] id;

		END_TIMER;
		//PRINT_TIMER_SEC("cpu_sort");
	}
	//an inefficient sorting method
	void sort() {
		INIT_TIMER;
		START_TIMER;

		vector<Item> data;
		for(unsigned int i = 0; i < _numAlign; i++) {
			data.push_back(Item(_readLength, get_qscore(i), _pos[i], 
					_hit[i], _strand[i], _offset[i], _sub[i]));
		}
		std::stable_sort(data.begin(), data.end(), ItemCompare);

		for(int i = 0; i < _numAlign; i++) {
			char* qscore = get_qscore(i);
			for(int k = 0; k < _readLength; k++) {
				qscore[k] = data[i]._qscore[k];
			}
			_pos[i] = data[i]._pos;
			_hit[i] = data[i]._hit;
			_strand[i] = data[i]._strand;
			_offset[i] = data[i]._offset;
			_sub[i] = data[i]._sub;
		}

		END_TIMER;
		printf("#element: %d\n", data.size());
		PRINT_TIMER_SEC("sorting time");
	}

	bool compare(const AlignResult* ref) {
		if(_numAlign != ref->_numAlign) {
			cout << "_numAlign != ref->_numAlign" << endl;
			return false;
		}
		if(_readLength != ref->_readLength) {
			cout << "_readLength != ref->readLength" << endl;
			return false;
		}
		
		if(!simpleCheck(ref->_qscore, _qscore, _readLength*_numAlign)) {
			return false;
		}

		if(!simpleCheck(ref->_pos, _pos, _numAlign)) {
			return false;
		}

		if(!simpleCheck(ref->_hit, _hit, _numAlign)) {
			return false;
		}

		if(!simpleCheck(ref->_strand, _strand, _numAlign)) {
			return false;
		}

		if(!simpleCheck(ref->_offset, _offset, _numAlign)) {
			return false;
		}

		if(!simpleCheck(ref->_sub, _sub, _numAlign)) {
			return false;
		}

		return true;
	}

	void close() {
		if(_numAlign > 0) {

			delete[] _qscore;
			delete[] _pos;
			delete[] _hit;
			delete[] _strand;
			delete[] _offset;
			delete[] _sub;

			_qscore = NULL;
			_pos = NULL;
			_hit = NULL;
			_strand = NULL;
			_offset = NULL;
			_sub = NULL;
		}
	}

	inline char* get_qscore(const unsigned int qid) {
		return _qscore + qid*_readLength;
	};

	inline void print_qscore(const unsigned int qid) {
		const char* qscore = get_qscore(qid);
		for(int i = 0; i < _readLength; i++) {
			printf("%c", qscore[i]);
		}
		cout << endl;
	}

	char getComplement(char base) {

		switch(base) {
		case 'a':
			return 't';
		case 'A':
			return 'T';
		case 't':
			return 'a';
		case 'T':
			return 'A';
		case 'c':
			return 'g';
		case 'C':
			return 'G';
		case 'g':
		case 'n':
			return 'c';
		case 'G':
		case 'N':
			return 'C';

		default: {
				 printf("character : %c \n", base);
				 ERROR_EXIT("unknown base");
			 }
		}
	}
	void reverse_complement(char* outRead, const char* inRead, const unsigned int readLength) {
		for(int i = 0; i < readLength; i++) {
			outRead[i] = getComplement(inRead[i]);
		}
		reverse(outRead, outRead + readLength);
	}

	inline void get_base(char* read, const unsigned int qid) {
		if(_ref == NULL) {
			cout << "!!!ERROR: The reference has not been assigned yet." << endl;
			exit(-1);
		} else {
			//copy the original reference
			for(int i = 0; i < _readLength; i++) {
				read[i] = _ref[_pos[qid] + i];
				if(read[i] > 'Z') {
					read[i] -= 32;
				}
			}
		
	
			/*if(_pos[qid] == 132666) {
				cout << "-----> " << _pos[qid] << endl;
				cout << "-----> " << read << endl;
				cout << "ddddd" << endl;
				printf("--> %d, %d\n", _offset[qid].x, _offset[qid].y);
				printf("--> %c, %c\n", _sub[qid].x, _sub[qid].y);
			}*/


			//cout << "copy:: " << read << endl; 
			//handle the first mismatch
			if(_sub[qid].x != -1) {
				assert(isBase(_sub[qid].x));
				read[_offset[qid].x] = _sub[qid].x;
			}
			//handle the second mismatch
			if(_sub[qid].y != -1) {
				assert(isBase(_sub[qid].y));
				read[_offset[qid].y] = _sub[qid].y;
			}
			//reverse, if '-' strand
			if(_strand[qid] == '-') {
				 reverse_complement(read, read, _readLength);
			}
		
		}
	}

	bool isBase(const char c) {
		if((c != 'A') && (c != 'T') && (c != 'C') && (c != 'G')) {
			return false;
		} else {
			return true;
		}
	}

	inline unsigned long getNumAlign() {
		return _numAlign;
	};

	void print() {
		cout << "_numAlign = " << _numAlign << ", _readLength = " << _readLength << endl;
		for(unsigned int i = 0; i < _numAlign; i++) {
			printf("%d\t%d\t%d\t%d\t%d\t%d\t%d\t", _pos[i], _hit[i], _strand[i], 
						_offset[i].x, _sub[i].x, _offset[i].y, _sub[i].y);
			const char* qscore = get_qscore(i);
			for(int j = 0; j < _readLength; j++) {
				printf("%d", qscore[j]);
			}
			cout << endl;
		}
	}
};
	

struct BucketPos {
	unsigned long _offset;	//byte from the beginning
	unsigned int _numAlign;	//the number of alignments written in the bucket

	BucketPos(unsigned long offset, unsigned int numAlign) {
		_offset = offset;
		_numAlign = numAlign;
	};

	BucketPos(){};
};


class AlignResultBase {
protected:

	/*meta-data*/
	string _chrName;
	unsigned int _readLength;
	unsigned int _refLength; 
	unsigned int _winSize;
	unsigned int _bucketSize;
	unsigned int _numBucket;

	string _fileName;
	FILE* _alignFile;
	FILE* _metaFile;
	vector<BucketPos>* _writePos;	//for each bucket, store the list of writting positions

	inline void printWritePos() {
		for(unsigned int bucketId = 0; bucketId < _numBucket; bucketId++) {
			cout << _writePos[bucketId].size() << ": ";
			vector<BucketPos>::iterator it;
			for(it = _writePos[bucketId].begin(); it < _writePos[bucketId].end(); it++) {
				cout << "<" << it->_offset << ", " << it->_numAlign << "> ";
			}
			cout << endl;
		}
	}

public:
	AlignResultBase(string chrName, unsigned int readLength, unsigned int refLength, unsigned int winSize, 
			const unsigned int bucketSize, const unsigned int numBucket, 
			string fileName) : 
		_chrName(chrName),
		_readLength(readLength), _refLength(refLength), _winSize(winSize), 
		_bucketSize(bucketSize), _numBucket(numBucket), _fileName(fileName) {

		_writePos = new vector<BucketPos>[_numBucket];
	};

	AlignResultBase() {};

	inline unsigned int getNumBucket() {return _numBucket;}
};


/**
 * this class is used to manage the alignment result, mainly these functions:
 * Note: this class is used to handle alignment with the same read length
 * 1. range partition, 
 * 2. compression, 
 * 3. then dump to the disk
 * */
class AlignResultWrite : public AlignResultBase {

public:
	AlignResultWrite(string chrName, unsigned int readLength, unsigned int refLength, 
			unsigned int winSize, unsigned int bucketSize, 
			string fileName);
	~AlignResultWrite();

	void insert(AlignResult* result);
	void close();

private:
	unsigned long _writeOffset;
	
	//we maintain vectors for each attribute for each bucket
	vector<char>*		_qscoreVec;
	vector<unsigned int>*	_posVec;
	vector<int>*		_hitVec;
	vector<char>* 		_strandVec;
	vector<uchar2>*		_offsetVec;
	vector<char2>* 		_subVec;

	const static int cmatrixSize = 256*256*4*4;
	ubit64_t countMatrix[cmatrixSize];

	//push one alginment to the buffer
	void push_back(AlignResult* result, const unsigned long id);
	//the buffer is full, dump the buffer to the disk
	void dump(const unsigned int bucketId);
	//reset the buffer
	void resetBucket(const unsigned int bucketId);
	inline unsigned int getBucketId(AlignResult* result, const unsigned long id);
	//update the count matrix
	void updateCountMatrix(const AlignResult* result, const unsigned long id);
};


/**
 * this class is used to read the data generated by AlignResultWrite
 * */
class AlignResultRead : public AlignResultBase {
public:

	AlignResultRead(string fileName);	
	~AlignResultRead();

	unsigned int fetchWindow(AlignResult* result, const unsigned int winId);
	bool fetchOrdered(char* read, char* qual, int* hit, int* readLen, 
				unsigned int* pos, char* strand, 
				uchar2* offset = NULL, char2* sub = NULL);
	inline void setRef(char* ref) {
		_resultBuf->setRef(ref);
	}

	string getChrName() {
		return _chrName;
	}

private:
	unsigned int _numInBuf;
	unsigned int _numWinRead;
	AlignResult* _resultBuf;
};


#endif /*__ALIGN_RESULT_H__*/
